/*
 * Dumper.java   2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.io.Serializable;
import java.util.Properties;

/**
 * <p>
 * This abstract class and its methods represent the Dumper, which is the wrapper
 *    of the stream used to unload the Entry.<br>
 *    The Dumper is created at Log startup and is builded with a custom set of properties
 *    prepared and loaded by the Log class. Every Dumper is then stored into a proper implementation
 * of {@link Register}. </p>
 * <p>
 *  When extending Dumper for creating a custom one, the Dumper will have a global
 *  set of variable taken either by the property file or if not found from the {@link
 *  Constant} interface. Other custom set of properties will have this basic rule.
 *  Giving the dumper.properties with this declaration:</p>
 * <p>dumper.name=<i>mydumper</i>,<i>otherdumper</i></p>
 * <p>Then the following properties has to have root:</p>
 * <p>
 * <blockquote>
 *  dumper.<i>mydumper</i>.property1=value<br>
 *  dumper.<i>mydumper</i>.property1=value<br>
 *  dumper.<i>otherdumper</i>.property3=value
 * </blockquote>
 * </p>
 * <p>
 *  In this way property1, property2 will be read only by <i>mydumper</i> and property3 only by
 *  <i>otherdumper</i>. The two dumpers cannot share each other their set of properties.
 * </p>
 * <p>
 *  For retrieving a custom property the method get(String name)
 *  serve the purpose. Supposing we want to get property <i>severity</i> for <i>mydumper</i>.
 * </p>
 * <p>
 *  <i>dumper.mydumper.severity=INFO</i><br/>
 *  is retrieved by calling:<br>
 *  <i>this.get("severity");</i><br/>
 *  The method will then reconstruct internally kind of:<br/>
 *  <i>property.getProperty(PROP_ROOT + name + key);</i><br/>
 *  and return <i>INFO</i><br/>
 * </p>
 * <p>
 *  Following is a list of global Dumper variables and their explanation, see the
 *  {@link Constant} interface for their default value.</p>
 * <ul>
 *    <li><i>killonfailure</i> = if a DumperException is thrown during operation and
 *        this value is true, then the Dumper.Status is set to FAILURE and the Dumper
 *        is invalid
 *    </li>
 *    <li><i>keepalive</i> = if true the dumper is always open</li>
 * </ul>
 * @author Leonardo Celati
 * @version 1.0
 */
public abstract class Dumper implements Constant, Serializable {
    
    /**
     * As required by serialization
     */
    private static final long serialVersionUID = 178957906062361835L;
    
    /**
     * Possible status
     */
    public enum Status {
        STARTUP,
        RUNNING,
        SLEEPING,
        CLOSE,
        FAILURE,
        KILLED,
    }
    /**
     * What happen on error...
     */
    private boolean killOnFailure;
    /**
     * The name of this Dumper
     */
    private String name;
    /**
     * Status for this Dumper
     */
    protected Status status;
    /**
     * Severity for this Dumper
     */
    private Severity severity;
    /**
     * Passed Properties for this Dumper
     */
    private Properties p;
    /**
     * Tell if we want to keep this Dumper always open
     */
    private boolean keepAlive;
    /**
     * See if we are open
     */
    protected boolean isOpen;
    
    
    /**
     * All the extended dumper are asked to call this constructor which will load the basic properties (severity and name are two of them).
     * @param p a custom set of properties
     */
    protected Dumper(Properties p) {
        assert(p==null || p.isEmpty()): "Properties empty or null";
        
        try {
            // startup...
            this.status=Status.STARTUP;
            
            // init our Dumper
            this.init(p);
            
            // ok...
            this.status = Status.RUNNING;
            
        } catch (RuntimeException e) {
            this.status=Status.FAILURE;
            throw new DumperInitializationException(e.getMessage());
        } catch (Exception e) {
            this.status=Status.FAILURE;
            throw new DumperInitializationException(e.getMessage());
        }
    }
    
    /**
     * Extended classes can override this method
     * to perform pre init operation
     */
    void preinit() {
    }
    
    /**
     * Init our Dumper, a call to preinit() and postinit() is also done
     * @param p custom set of properties
     * @throws java.lang.Exception if something went wrong
     */
    private void init(Properties p) throws Exception {
        // we set the name then we start
        // setting the basic properties
        this.name = p.getProperty("name");
        // then we remove...
        p.remove("name");
        
        // store locally
        this.p=p;
        
        // extending classes can perform their
        // pre operation by overridding this method
        this.preinit();
        
        // severity for this Dumper, cannot have a default value
        // that's why we throw error if we cannot parse the property
        try {
            this.severity=Severity.valueOf( ((String)get("severity")).toUpperCase() );
        } catch (Exception ex) {
            throw new DumperInitializationException("unknown severity for " + this.name);
        }
        
        // kill ?
        try {
            this.killOnFailure = Boolean.parseBoolean( get("killonfailure").toLowerCase() );
        } catch (Exception e) {
            this.killOnFailure = DEFAULT_KILL_ON_FAILURE;
        }
        
        // keep alive ?
        try {
            this.keepAlive=Boolean.parseBoolean(get("keepalive").toLowerCase());
        } catch (Exception e) {
            this.keepAlive = DEFAULT_KEEP_ALIVE;
        }
        
        // time to call post init...
        this.postinit();
        
        // opening...
        if (this.keepAlive) {
            this.prepare(true);
        }
    }
    
    /**
     * Extended classes can override this method
     * to perform their post init operation
     */
    void postinit() {
    }
    
    /**
     * Get specific property from our property set...
     * @param name the property to look for. Only the key not all prefix, e dumper.mydumper.[name] must be simply searched with [name]
     * @return A string containing the value or null it doesn't exist
     */
    protected String get(String name) {
        // browse out property set
        for (Object key: p.keySet()) {
            if (((String)key).indexOf(Constant.PROP_ROOT + this.getName() + "." + name) > -1) {
                return ((String) p.get(key)).trim();
            }
        }
        // if nothing found...
        return null;
    }
    
    /**
     * Getter for status
     * @return Status
     */
    public final Status getStatus() {
        return this.status;
    }
     
    /**
     * Getter for name
     * @return String
     */
    public final String getName() {
        return name;
    }
    
    /**
     * Getter for kill on failure
     * @return Severity
     */
    public final boolean hasKillOnFailure() {
        return killOnFailure;
    }
    
    /**
     * We want this dumper always open
     * @return Severity
     */
    public final boolean isKeepAlive() {
        return this.keepAlive;
    }
    
    /**
     * Getter for severity
     * @return Severity
     */
    public final Severity getSeverity() {
        return severity;
    }
    
    /**
     * True or false if this Dumper is open
     * @return true/false
     */
    public final boolean isOpen() {
        return this.isOpen;
    }
    
    /**
     * Internal method to call for force the opening of the Dumper
     * @param force true/false
     * @throws DumperException
     */
    private void prepare(boolean force) throws DumperException {
        try {
            // if force...we of course force the opening....
            if (force) {
                // if it's already opened we do nothing...
                // otherwise we open the Dumper
                if (this.isOpen) {
                    //throw new DumperException("Cannot force already open");
                    return;
                }
                this.open();
            } else {
                // if it's not keep alive and not open
                // we can call open...otherwise nothing is done...
                if (!this.keepAlive && !this.isOpen() ) {
                    this.open();
                }  else {
                    return;
                }
            }
        } catch (DumperException de) {
            // if we get here something went wrong
            // depends on flag we close the Dumper
            if (this.killOnFailure)
                this.status = Status.FAILURE;
            // then re throw the error...
            throw new DumperException(de);
        }
    }
    
    /**
     * Method called by the QueueDumper to prepare the Dumper itself.
     * @throws DumperException
     * @throws it.wzm.lightweightlog.DumperException if something went wrong
     */
    final void prepare() throws DumperException {
        this.prepare(false);
    }
    
    /**
     * Extending classes can implement this method and put here all the operations for opening the proper channel.
     * @throws DumperException
     * @throws it.wzm.lightweightlog.DumperException if something went wrong
     */
    protected void open() throws DumperException {
    }
    
    /**
     * Unload the Entry to the stream, a call to postdump is also done.
     * @param e A passed object extending {@link Entry}
     * @throws DumperException if something went wrong
     */
    final <E extends Entry> void dump(E e) throws DumperException {
        // just deciding if we need to unload this bean
        // by simply comparing to dumper severity
        if (e.getSeverity().compareTo(this.getSeverity()) > -1) {
            try {
                // check if we already open...
                
                // now calling write...
                write(e);
            } catch (DumperException de) {
                if (this.killOnFailure) {
                    this.status=Status.FAILURE;
                }
                throw new DumperException(de);
            }
        }
    }
    
    /**
     * Write Entry to the preferred channel of the Dumper.
     * Extending classes has to implement this method if they want to unload the Entry.
     * @param e Anything extending Entry
     * @throws it.wzm.lightweightlog.DumperException if something went wrong
     */
    protected abstract <E extends Entry> void write(E e) throws DumperException;
    
    /**
     * Shortcut for release(false)
     * if force is true
     * @param force if true force release
     */
    final void release() throws DumperException {
        this.release(false);
    }
    
    /**
     * If force is true this dumper is going to be closed in any case
     * @throws DumperException
     * @throws it.wzm.lightweightlog.DumperException if something went wrong
     */
    final void release(boolean force) throws DumperException {
        try {
            if (!force) {
                // deciding if we are alive...
                if (!this.keepAlive) {
                    // calling the abstract method
                    // we let user implement it...
                    close();
                    this.isOpen=false;
                    this.status=Status.CLOSE;
                } else {
                    // everything works...
                    this.status=Status.SLEEPING;
                }
            } else {
                close();
                this.isOpen=false;
            }
        } catch (DumperException de) {
            // if we get here something went wrong
            // depends on flag we close the Dumper
            if (this.killOnFailure)
                this.status = Status.FAILURE;
            // then re throw the error...
            throw new DumperException(de);
        }
    }
    
    /**
     * Extending classes are asked to implements this method,
     * any close or finalize action will goes here.
     * which close the logger
     * @throws DumperException
     */
    protected void close() throws DumperException {
    }
    
    /**
     * Kill this dumper and free resources, dumper can have his own
     * clean method implementing postkill
     */
    public final void kill() {
        // kill mode...
        this.status = Status.KILLED;
        this.postkill();
    }
    
    /**
     * Dumper can implement this method
     */
    public void postkill() {
    }
    
    
    /**
     * Overwriting the inherited method from Object
     */
    @Override
    public String toString() {
        
        StringBuffer sb = new StringBuffer();
        
        for (Object key: p.keySet()) {
            if (((String)key).indexOf(Constant.PROP_ROOT + this.name + ".") > -1) {
                sb.append( "," +  ((String)key).replaceAll(Constant.PROP_ROOT + this.name + ".", "") + "=" +  p.get(key) );
            }
        }
        //  returning...
        return super.toString() + " {hc=" + this.hashCode() + "," + this.status + sb + "}" ;
    }
    
}
